Scoping and Textual Inclusion

Earlier we saw that the contours created by the LET special function are drawn with hollow instead of solid arrows. This is because LET creates contours that are local to other contours. In order to explain this further, let's examine the LET function in more detail.

LET can be used to create any number of variables, with the property that the creation of the variables and assignment of values to them is carried out ``in parallel.'' The following (slightly unusual) definition of a function for computing the greatest common divisor of two integers demonstrates a use of this parallel feature.


\begin{code}
(defun gcd (x y)
(if (= x y)
x
(let ((x (if (< x y) x y))
(y (if (< x y) y x)))
(gcd x (- y x)))))
\end{code}

The LET expression in GCD is used to ensure that X is less than Y, swapping their values if necessary, before evaluating the recursive call in the body. Doing the swap correctly depends on the parallel nature of LET, as the evaltrace given in Figure [*] shows.

Figure: Evaltrace diagram illustrating LET.
\begin{figure}{\noindent\rule{\textwidth}{.01in}}
\par
\begin{evaltrace}
+--> ;(...
... is 6
\end{evaltrace}\par\par
{\noindent\rule{\textwidth}{.01in}}
\end{figure}

The form of evaltrace diagrams for LET expressions exposes the fact that the expressions (if (< x y) ) are evaluated in a context that cannot possibly be affected by the ``new'' versions of the variables X and Y created in the LET body. Therefore, these variables are effectively created and assigned in ``parallel.''

Recall from before that the hollow arrow drawn for the LET body's contour indicates that its parent is the immediately enclosing contour. This nesting of contours (i.e., the child-to-parent relationships between contours) is completely determined by the textual inclusion of LET bodies in functions. In the above example, the parent for the local contour created by the LET expression is GCD's contour because GCD textually includes the LET expression. This is, in fact, a general rule for lexical scoping in Lisp: a contour A can be the parent of another contour B only if the Lisp code corresponding to contour A textually includes the code for B.

This all implies, also, that a function's environment always mirrors the textual inclusions in the code. This fact is important for understanding closures, which we discuss in the next section.

Getting back to our discussion of LET, it should be noted that this parallel behavior of LET is not always what one desires when creating local variables. For example:


\begin{code}
(defun price-change (name old new)
(let ((diff (- new old))
(prop...
...* proportion 100.0)))
(list name 'changed 'by percentage 'percent)))
\end{code}

Figure: Evaltrace illustrating an incorrect use of LET.
\begin{figure}{\noindent\rule{\textwidth}{.01in}}
\par
\begin{evaltrace}
+--> ;(...
...able.
\end{evaltrace}\par\par
{\noindent\rule{\textwidth}{.01in}}
\end{figure}

Calling this function leads to an error, as illustrated by the evaltrace in Figure [*]. The value of DIFF is needed in order to compute the value for PROPORTION, but the local variable DIFF has not yet been created. Therefore the reference to DIFF is interpreted incorrectly as a reference to a global variable by that name. The global variable DIFF has not been assigned a value (or one might say it doesn't exist), hence the error message. The problem can be remedied by carrying out the creation and assignment of the three local variables serially, using the LET* special function in place of LET.


\begin{code}
(defun price-change (name old new)
(let* ((diff (- new old))
(pro...
...* proportion 100.0)))
(list name 'changed 'by percentage 'percent)))
\end{code}

Figure: Evaltrace diagram illustrating LET*.
\begin{figure}{\noindent\rule{\textwidth}{.01in}}
\par
\begin{evaltrace}
+--> ;(...
...CENT)
\end{evaltrace}\par\par
{\noindent\rule{\textwidth}{.01in}}
\end{figure}

The evaltrace for this in Figure [*] shows that LET* generates a new, nested contour for each variable it creates. The diagram suggests that LET* expressions behave like nested LET expressions.